//--------------------------------------------------------------------
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "dkinteractive.h"
#include "dkstring.h"
#include "dkbasic.h"
#include "dkinterpreter.h"
#include "dkprogram.h"
#include "dkvariables.h"
#include "dkstatements.h"
#include "dkterm.h"
#include "dkfile_uart.h"
#include "dkevents.h"

//--------------------------------------------------------------------
extern char *__malloc_heap_start;
extern char *__malloc_heap_end;

extern char *__brkval;



//--------------------------------------------------------------------
void dkinteractive(void)
{
	char line[MAX_LINE_LENGTH+1];
	char token[MAX_TOKEN_LENGTH+1];
	char *lptr;
	struct _dkprogline *proglineptr;
	uint16_t memfree;


	printf_P( PSTR("\n\n") );
	dkterm_clearscreen();
	printf_P( PSTR("DK-BASIC %1.2f   %s %s\n"), DKBASIC_VERNUM/100.0, __DATE__, __TIME__ );


// calculate (approximate) # of bytes free for the heap
// this should be handled in fre() function
	memfree = (uint16_t)__brkval;
	if(!memfree)
		memfree = (uint16_t)__malloc_heap_start;

	if( __malloc_heap_end )
		memfree = (uint16_t)__malloc_heap_end - memfree;
	else
		memfree = RAMEND - memfree;
	printf_P( PSTR("%d Bytes free\n"), memfree );

		
										// initialize globals used by the interpreter
	dkPC = 0;
	dkprevPC = 0;
	dktrace = 0;						// trace off by default

	dkvariables = NULL;
	dkprogram = NULL;
	dkfornextstack = NULL;
	dkwhilewendstack = NULL;
	dkgosubstack = NULL;
	

	dkloadprogram( "eep:" );			// load default program from EEP:
	if( !dkprogram )
		dkloadprogram( "fla:" );		// if no default program in eep, load default program from FLA:
	dkrunprogram();						// run startup.bas

	dknewprogram();

	printf_P( PSTR("Ok\n") );			// display 'Ok' prompt


	while( 1 )                       	// loop forever in interpreter
	{
		while( io_ctrl_char == DKKEY_PAUSE )
			;									// if pause was pressed, wait

		if( dkstatus == DKSTATUS_EDIT )
		{
			proglineptr = dkgetclosestprogline( dkprevPC );
			if( proglineptr && (proglineptr->num == dkprevPC) )
			{
				dkstatus = DKSTATUS_OK;
				sprintf_P( line, PSTR("%d %s"), proglineptr->num, proglineptr->line );
				dkpregets( line );
			}
			else
			{
				dkstatus = DKSTATUS_UNDEFINEDLINENUMBER;
				dkerrorhandler( dkstatus );
				continue;
			}
		}
		else
			dkgets( line );

		printf_P( PSTR("\n") );

		if( !strlen( line ) )
			continue;

		lptr = dkgettoken( line, token );
		if( !strlen(token) )
			continue;

		while( io_ctrl_char == DKKEY_PAUSE )
			;									// if pause was pressed, wait

		io_ctrl_char = 0;				// reset flag that signals a control character (ctrl-c) was received

		if( atoi(token) > 0 )
		{								// got a line number
			if( dkPC )
				dkprevPC = dkPC;
			dkPC = 0;
			dkstatus = DKSTATUS_OK;

			if( lptr[0] )				// if the line is not blank, skip over the first space
				lptr++;
			dksetprogline( atoi(token), lptr );
				
			if( dkstatus )
				dkerrorhandler( dkstatus );
			else
				dkPC = atoi(token);			// update PC = last entered line (LIST should print this line with LIST . - but doesn't because dkPC is reset to 0)
			dkstatus = DKSTATUS_OK;
		}
		else
		{		
			if( dkPC )
				dkprevPC = dkPC;
			dkPC = 0;					// PC = 0 (not part of program, immediate)

			while( dkPC == 0 )
			{


				while( io_ctrl_char == DKKEY_PAUSE )
					;									// if pause was pressed, wait

				if( io_ctrl_char == DKKEY_BREAK )
				{
					while( _kbhit() )
					{
						if( _getch() == DKKEY_BREAK )
							break;
					}

					dkstatus = DKSTATUS_BREAK;
					io_ctrl_char = 0;					// reset uart_ctrl_char (it's been noticed)
					break;							// ctrl-C was received - break
				}

				dkinterpretline( line );
	
				switch( dkstatus )
				{
					case DKSTATUS_OK:
						break;

					case DKSTATUS_END:
						dkprevPC = dkPC;
						break;

					case DKSTATUS_GOTO:
						if( dkPC == 0 )
							continue;		// if dkPC == 0 then its a loop on the entered line

						dkrunprogram();		// run program starting from PC set by goto
						break;

					case DKSTATUS_RETURN:
						dkstatus = DKSTATUS_OK;	// nowhere to return to in interactive mode - we're done interpreting for now
						break;

	
					case DKSTATUS_SKIPTONEXT:
						dkstatus = DKSTATUS_FORWITHOUTNEXT;
						if( dkfornextstack )
							dkPC = dkfornextstack->forlinenum;
						break;				// 'FOR without NEXT error will be displayed


					default:				// error
						break;
				}
				break;
			}

						// error - check if error handler installed for this error, if not then display error
			dkerrorhandler( dkstatus );									
			
			if( dkstatus != DKSTATUS_EDIT )
				printf_P( PSTR("Ok\n") );	// display 'Ok' prompt

			if( (dkstatus>=DKSTATUS_FIRSTERROR) && dkPC )
			{
				dkprevPC = dkPC;
				dkstatus = DKSTATUS_EDIT;
			}
		}
	}
} 


//--------------------------------------------------------------------

void dkerrorhandler( uint16_t stat )
{
	struct _dk_errorhandler *errorhandlerptr;
	


	for( errorhandlerptr=dk_errorhandlers; errorhandlerptr; errorhandlerptr=errorhandlerptr->next )
	{
		if( stat == errorhandlerptr->errornum )
			break;			// found matching errorhandler, will goto errorhandlerptr->linenum
	}	
	

	if( dkscreen.col > 1 )
		printf_P( PSTR("\n") );

	if( stat < DKSTATUS_FIRSTERROR )
		return;

	switch( stat )
	{
		case DKSTATUS_SYNTAXERROR:
			printf_P( PSTR("Syntax error") );
			break;

		case DKSTATUS_MISSINGOPERAND:
			printf_P( PSTR("Missing operand") );
			break;

		case DKSTATUS_TYPEMISMATCH:
			printf_P( PSTR("Type mismatch") );
			break;

		case DKSTATUS_ILLEGALFUNCTIONCALL:
			printf_P( PSTR("Illegal function call") );
			break;

		case DKSTATUS_OUTOFMEMORY:
			printf_P( PSTR("Out of memory") );
			break;

		case DKSTATUS_UNDEFINEDLINENUMBER:
			printf_P( PSTR("Undefined line number") );
			break;

		case DKSTATUS_IFWITHOUTTHEN:
			printf_P( PSTR("IF without THEN") );
			break;
	
		case DKSTATUS_BREAK:
			printf_P( PSTR("Break") );
			break;
	
		case DKSTATUS_NEXTWITHOUTFOR:
			printf_P( PSTR("NEXT without FOR") );
			break;
	
		case DKSTATUS_FORWITHOUTNEXT:
			printf_P( PSTR("FOR without NEXT") );
			break;

		case DKSTATUS_WENDWITHOUTWHILE:
			printf_P( PSTR("WEND without WHILE") );
			break;

		case DKSTATUS_RETURNWITHOUTGOSUB:
			printf_P( PSTR("RETURN without GOSUB") );
			break;

		case DKSTATUS_DEVICEUNAVAILABLE:
			printf_P( PSTR("Device Unavailable") );
			break;

		case DKSTATUS_BADFILENUMBER:
			printf_P( PSTR("Bad file number") );
			break;
			
		case DKSTATUS_DEVICEIOERROR:
			printf_P( PSTR("Device I/O Error") );
			break;

		case DKSTATUS_SUBSCRIPTOUTOFRANGE:
			printf_P( PSTR("Subscript out of range") );
			break;
			
		case DKSTATUS_FILENOTFOUND:
			printf_P( PSTR("File not found") );
			break;
			
		default:											// TODO: handle the rest of the errors
			printf_P( PSTR("Unknown error") );
			break;
	}

	if( dkPC )
		printf_P( PSTR(" in %d\n"), dkPC );
	else
		printf_P( PSTR("\n") );			// newline at end of error


	return;
}


//--------------------------------------------------------------------


